home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Libris Britannia 4
/
science library(b).zip
/
science library(b)
/
DDJMAG
/
DDJ9207.ZIP
/
GRPHPROG.ASC
< prev
next >
Wrap
Text File
|
1992-06-15
|
15KB
|
351 lines
_GRAPHICS PROGRAMMING COLUMN_
by Michael Abrash
[LISTING ONE]
; Fixed point multiply and divide routines. Tested with TASM 3.0.
USE386 equ 1 ;1 for 386-specific opcodes, 0 for 8088 opcodes
MUL_ROUNDING_ON equ 1 ;1 for rounding on multiplies, 0 for no
; rounding. Not rounding is faster, rounding is
; more accurate and generally a good idea
DIV_ROUNDING_ON equ 0 ;1 for rounding on divides, 0 for no rounding.
; Not rounding is faster, rounding is more
; accurate, but because division is only
; performed to project to the screen, rounding
; quotients generally isn't necessary
ALIGNMENT equ 2
.model small
.386
.code
;=====================================================================
; Multiplies two fixed-point values together. C near-callable as:
; Fixedpoint FixedMul(Fixedpoint M1, Fixedpoint M2);
FMparms struc
dw 2 dup(?) ;return address & pushed BP
M1 dd ?
M2 dd ?
FMparms ends
align ALIGNMENT
public _FixedMul
_FixedMul proc near
push bp
mov bp,sp
if USE386
mov eax,[bp+M1]
imul dword ptr [bp+M2] ;multiply
if MUL_ROUNDING_ON
add eax,8000h ;round by adding 2^(-17)
adc edx,0 ;whole part of result is in DX
endif ;MUL_ROUNDING_ON
shr eax,16 ;put the fractional part in AX
else ;!USE386
;do four partial products and add them
; together, accumulating the result in CX:BX
push si ;preserve C register variables
push di
;figure out signs, so we can use unsigned multiplies
sub cx,cx ;assume both operands positive
mov ax,word ptr [bp+M1+2]
mov si,word ptr [bp+M1]
and ax,ax ;first operand negative?
jns CheckSecondOperand ;no
neg ax ;yes, so negate first operand
neg si
sbb ax,0
inc cx ;mark that first operand is negative
CheckSecondOperand:
mov bx,word ptr [bp+M2+2]
mov di,word ptr [bp+M2]
and bx,bx ;second operand negative?
jns SaveSignStatus ;no
neg bx ;yes, so negate second operand
neg di
sbb bx,0
xor cx,1 ;mark that second operand is negative
SaveSignStatus:
push cx ;remember sign of result; 1 if result
; negative, 0 if result nonnegative
push ax ;remember high word of M1
mul bx ;high word M1 times high word M2
mov cx,ax ;accumulate result in CX:BX (BX not used until
; next operation) assume no overflow into DX
mov ax,si ;low word M1 times high word M2
mul bx
mov bx,ax
add cx,dx ;accumulate result in CX:BX
pop ax ;retrieve high word of M1
mul di ;high word M1 times low word M2
add bx,ax
adc cx,dx ;accumulate result in CX:BX
mov ax,si ;low word M1 times low word M2
mul di
if MUL_ROUNDING_ON
add ax,8000h ;round by adding 2^(-17)
adc bx,dx
else ;!MUL_ROUNDING_ON
add bx,dx ;don't round
endif ;MUL_ROUNDING_ON
adc cx,0 ;accumulate result in CX:BX
mov dx,cx
mov ax,bx
pop cx
and cx,cx ;is the result negative?
jz FixedMulDone ;no, we're all set
neg dx ;yes, so negate DX:AX
neg ax
sbb dx,0
FixedMulDone:
pop di ;restore C register variables
pop si
endif ;USE386
pop bp
ret
_FixedMul endp
;=====================================================================
; Divides one fixed-point value by another. C near-callable as:
; Fixedpoint FixedDiv(Fixedpoint Dividend, Fixedpoint Divisor);
FDparms struc
dw 2 dup(?) ;return address & pushed BP
Dividend dd ?
Divisor dd ?
FDparms ends
align ALIGNMENT
public _FixedDiv
_FixedDiv proc near
push bp
mov bp,sp
if USE386
if DIV_ROUNDING_ON
sub cx,cx ;assume positive result
mov eax,[bp+Dividend]
and eax,eax ;positive dividend?
jns FDP1 ;yes
inc cx ;mark it's a negative dividend
neg eax ;make the dividend positive
FDP1: sub edx,edx ;make it a 64-bit dividend, then shift
; left 16 bits so that result will be in EAX
rol eax,16 ;put fractional part of dividend in
; high word of EAX
mov dx,ax ;put whole part of dividend in DX
sub ax,ax ;clear low word of EAX
mov ebx,dword ptr [bp+Divisor]
and ebx,ebx ;positive divisor?
jns FDP2 ;yes
dec cx ;mark it's a negative divisor
neg ebx ;make divisor positive
FDP2: div ebx ;divide
shr ebx,1 ;divisor/2, minus 1 if the divisor is
adc ebx,0 ; even
dec ebx
cmp ebx,edx ;set Carry if the remainder is at least
adc eax,0 ; half as large as the divisor, then
; use that to round up if necessary
and cx,cx ;should the result be made negative?
jz FDP3 ;no
neg eax ;yes, negate it
FDP3:
else ;!DIV_ROUNDING_ON
mov edx,[bp+Dividend]
sub eax,eax
shrd eax,edx,16 ;position so that result ends up
sar edx,16 ; in EAX
idiv dword ptr [bp+Divisor]
endif ;DIV_ROUNDING_ON
shld edx,eax,16 ;whole part of result in DX;
; fractional part is already in AX
else ;!USE386
;NOTE!!! Non-386 division uses a 32-bit dividend but only the upper 16 bits
; of the divisor; in other words, only the integer part of the divisor is
; used. This is done so that the division can be accomplished with two fast
; hardware divides instead of a slow software implementation, and is (in my
; opinion) acceptable because division is only used to project points to the
; screen (normally, the divisor is a Z coordinate), so there's no cumulative
; error, although there will be some error in pixel placement (the magnitude
; of the error is less the farther away from the Z=0 plane objects are). This
; is *not* a general-purpose divide, though; if the divisor is less than 1,
; for instance, a divide-by-zero error will result! For this reason, non-386
; projection can't be performed for points closer to the viewpoint than Z=1.
;figure out signs, so we can use
; unsigned divisions
sub cx,cx ;assume both operands positive
mov ax,word ptr [bp+Dividend+2]
and ax,ax ;first operand negative?
jns CheckSecondOperandD ;no
neg ax ;yes, so negate first operand
neg word ptr [bp+Dividend]
sbb ax,0
inc cx ;mark that first operand is negative
CheckSecondOperandD:
mov bx,word ptr [bp+Divisor+2]
and bx,bx ;second operand negative?
jns SaveSignStatusD ;no
neg bx ;yes, so negate second operand
neg word ptr [bp+Divisor]
sbb bx,0
xor cx,1 ;mark that second operand is negative
SaveSignStatusD:
push cx ;remember sign of result; 1 if result
; negative, 0 if result nonnegative
sub dx,dx ;put Dividend+2 (integer part) in DX:AX
div bx ;first half of 32/16 division, integer part
; divided by integer part
mov cx,ax ;set aside integer part of result
mov ax,word ptr [bp+Dividend] ;concatenate the fractional part of
; the dividend to the remainder (fractional
; part) of the result from dividing the
; integer part of the dividend
div bx ;second half of 32/16 division
if DIV_ROUNDING_ON EQ 0
shr bx,1 ;divisor/2, minus 1 if the divisor is
adc bx,0 ; even
dec bx
cmp bx,dx ;set Carry if the remainder is at least
adc ax,0 ; half as large as the divisor, then
adc cx,0 ; use that to round up if necessary
endif ;DIV_ROUNDING_ON
mov dx,cx ;absolute value of result in DX:AX
pop cx
and cx,cx ;is the result negative?
jz FixedDivDone ;no, we're all set
neg dx ;yes, so negate DX:AX
neg ax
sbb dx,0
FixedDivDone:
endif ;USE386
pop bp
ret
_FixedDiv endp
end
[LISTING TWO]
/* Draws all visible faces in the specified polygon-based object. The object
must have previously been transformed and projected, so that all vertex
arrays are filled in. Ambient and diffuse shading are supported. */
#include "polygon.h"
void DrawPObject(PObject * ObjectToXform)
{
int i, j, NumFaces = ObjectToXform->NumFaces, NumVertices;
int * VertNumsPtr, Spot;
Face * FacePtr = ObjectToXform->FaceList;
Point * ScreenPoints = ObjectToXform->ScreenVertexList;
PointListHeader Polygon;
Fixedpoint Diffusion;
ModelColor ColorTemp;
ModelIntensity IntensityTemp;
Point3 UnitNormal, *NormalStartpoint, *NormalEndpoint;
long v1, v2, w1, w2;
Point Vertices[MAX_POLY_LENGTH];
/* Draw each visible face (polygon) of the object in turn */
for (i=0; i<NumFaces; i++, FacePtr++) {
/* Remember where we can find the start and end of the polygon's
unit normal in view space, and skip over the unit normal endpoint
entry. The end and start points of the unit normal to the polygon
must be the first and second entries in the polgyon's vertex list.
Note that the second point is also an active polygon vertex */
VertNumsPtr = FacePtr->VertNums;
NormalEndpoint = &ObjectToXform->XformedVertexList[*VertNumsPtr++];
NormalStartpoint = &ObjectToXform->XformedVertexList[*VertNumsPtr];
/* Copy over the face's vertices from the vertex list */
NumVertices = FacePtr->NumVerts;
for (j=0; j<NumVertices; j++)
Vertices[j] = ScreenPoints[*VertNumsPtr++];
/* Draw only if outside face showing (if the normal to the polygon
in screen coordinates points toward the viewer; that is, has a
positive Z component) */
v1 = Vertices[1].X - Vertices[0].X;
w1 = Vertices[NumVertices-1].X - Vertices[0].X;
v2 = Vertices[1].Y - Vertices[0].Y;
w2 = Vertices[NumVertices-1].Y - Vertices[0].Y;
if ((v1*w2 - v2*w1) > 0) {
/* It is facing the screen, so draw */
/* Appropriately adjust the extent of the rectangle used to
erase this object later */
for (j=0; j<NumVertices; j++) {
if (Vertices[j].X >
ObjectToXform->EraseRect[NonDisplayedPage].Right)
if (Vertices[j].X < SCREEN_WIDTH)
ObjectToXform->EraseRect[NonDisplayedPage].Right =
Vertices[j].X;
else ObjectToXform->EraseRect[NonDisplayedPage].Right =
SCREEN_WIDTH;
if (Vertices[j].Y >
ObjectToXform->EraseRect[NonDisplayedPage].Bottom)
if (Vertices[j].Y < SCREEN_HEIGHT)
ObjectToXform->EraseRect[NonDisplayedPage].Bottom =
Vertices[j].Y;
else ObjectToXform->EraseRect[NonDisplayedPage].Bottom=
SCREEN_HEIGHT;
if (Vertices[j].X <
ObjectToXform->EraseRect[NonDisplayedPage].Left)
if (Vertices[j].X > 0)
ObjectToXform->EraseRect[NonDisplayedPage].Left =
Vertices[j].X;
else ObjectToXform->EraseRect[NonDisplayedPage].Left=0;
if (Vertices[j].Y <
ObjectToXform->EraseRect[NonDisplayedPage].Top)
if (Vertices[j].Y > 0)
ObjectToXform->EraseRect[NonDisplayedPage].Top =
Vertices[j].Y;
else ObjectToXform->EraseRect[NonDisplayedPage].Top=0;
}
/* See if there's any shading */
if (FacePtr->ShadingType == 0) {
/* No shading in effect, so just draw */
DRAW_POLYGON(Vertices, NumVertices, FacePtr->ColorIndex, 0, 0);
} else {
/* Handle shading */
/* Do ambient shading, if enabled */
if (AmbientOn && (FacePtr->ShadingType & AMBIENT_SHADING)) {
/* Use the ambient shading component */
IntensityTemp = AmbientIntensity;
} else {
SET_INTENSITY(IntensityTemp, 0, 0, 0);
}
/* Do diffuse shading, if enabled */
if (FacePtr->ShadingType & DIFFUSE_SHADING) {
/* Calculate the unit normal for this polygon, for use in dot
products */
UnitNormal.X = NormalEndpoint->X - NormalStartpoint->X;
UnitNormal.Y = NormalEndpoint->Y - NormalStartpoint->Y;
UnitNormal.Z = NormalEndpoint->Z - NormalStartpoint->Z;
/* Calculate the diffuse shading component for each active
spotlight */
for (Spot=0; Spot<MAX_SPOTS; Spot++) {
if (SpotOn[Spot] != 0) {
/* Spot is on, so sum, for each color component, the
intensity, accounting for the angle of the light rays
relative to the orientation of the polygon */
/* Calculate cosine of angle between the light and the
polygon normal; skip if spot is shining from behind
the polygon */
if ((Diffusion = DOT_PRODUCT(SpotDirectionView[Spot],
UnitNormal)) > 0) {
IntensityTemp.Red +=
FixedMul(SpotIntensity[Spot].Red, Diffusion);
IntensityTemp.Green +=
FixedMul(SpotIntensity[Spot].Green, Diffusion);
IntensityTemp.Blue +=
FixedMul(SpotIntensity[Spot].Blue, Diffusion);
}
}
}
}
/* Convert the drawing color to the desired fraction of the
brightest possible color */
IntensityAdjustColor(&ColorTemp, &FacePtr->FullColor,
&IntensityTemp);
/* Draw with the cumulative shading, converting from the general
color representation to the best-match color index */
DRAW_POLYGON(Vertices, NumVertices,
ModelColorToColorIndex(&ColorTemp), 0, 0);
}
}
}
}